{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "54b83f29-aaac-48c5-96d0-c7306a4829c4",
   "metadata": {},
   "source": [
    "# Lesson 4: Constructing a Knowledge Graph from Text Documents"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a87f162a-036e-4f92-b747-7ccfc6dc7b70",
   "metadata": {},
   "source": [
    "<p style=\"background-color:#fd4a6180; padding:15px; margin-left:20px\"> <b>Note:</b> This notebook takes about 30 seconds to be ready to use. Please wait until the \"Kernel starting, please wait...\" message clears from the top of the notebook before running any cells. You may start the video while you wait.</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebcbf000-c1dc-4859-bf14-cbc84eb32053",
   "metadata": {},
   "source": [
    "### Import packages and set up Neo4j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8570032f-9008-4d8a-8232-59866218e01e",
   "metadata": {
    "height": 336
   },
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "import os\n",
    "\n",
    "# Common data processing\n",
    "import json\n",
    "import textwrap\n",
    "\n",
    "# Langchain\n",
    "from langchain_community.graphs import Neo4jGraph\n",
    "from langchain_community.vectorstores import Neo4jVector\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "from langchain.chains import RetrievalQAWithSourcesChain\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "# Warning control\n",
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d4edcf01-cac6-4539-a8c3-db5b175a41b2",
   "metadata": {
    "height": 302
   },
   "outputs": [],
   "source": [
    "# Load from environment\n",
    "load_dotenv('.env', override=True)\n",
    "NEO4J_URI = os.getenv('NEO4J_URI')\n",
    "NEO4J_USERNAME = os.getenv('NEO4J_USERNAME')\n",
    "NEO4J_PASSWORD = os.getenv('NEO4J_PASSWORD')\n",
    "NEO4J_DATABASE = os.getenv('NEO4J_DATABASE') or 'neo4j'\n",
    "OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')\n",
    "# Note the code below is unique to this course environment, and not a \n",
    "# standard part of Neo4j's integration with OpenAI. Remove if running \n",
    "# in your own environment.\n",
    "OPENAI_ENDPOINT = os.getenv('OPENAI_BASE_URL') + '/embeddings'\n",
    "\n",
    "# Global constants\n",
    "VECTOR_INDEX_NAME = 'form_10k_chunks'\n",
    "VECTOR_NODE_LABEL = 'Chunk'\n",
    "VECTOR_SOURCE_PROPERTY = 'text'\n",
    "VECTOR_EMBEDDING_PROPERTY = 'textEmbedding'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad8c6cb6-74ba-4d94-81d2-9bca5893c44a",
   "metadata": {},
   "source": [
    "### Take a look at a Form 10-K json file\n",
    "\n",
    "- Publicly traded companies are required to fill a form 10-K each year with the Securities and Exchange Commision (SEC)\n",
    "- You can search these filings using the SEC's [EDGAR database](https://www.sec.gov/edgar/search/)\n",
    "- For the next few lessons, you'll work with a single 10-K form for a company called [NetApp](https://www.netapp.com/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d29dc84a-a51d-4f48-8dae-6c9c39565299",
   "metadata": {
    "height": 47
   },
   "outputs": [],
   "source": [
    "first_file_name = \"./data/form10k/0000950170-23-027948.json\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9d6697f-e8f1-4e1a-aa6d-0d641fbc3649",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "first_file_as_object = json.load(open(first_file_name))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4d3ef32c-6f10-430c-83b4-f34e54fb45d8",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "type(first_file_as_object)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58bd1c0f-bca0-4f16-846a-8158967b61e8",
   "metadata": {
    "height": 47
   },
   "outputs": [],
   "source": [
    "for k,v in first_file_as_object.items():\n",
    "    print(k, type(v))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e431a995-84c6-40ee-854f-8f09d7e43259",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "item1_text = first_file_as_object['item1']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aee07cd1-00c4-477b-9f05-5f0db5cef432",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "item1_text[0:1500]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf3d09a3-25a2-4f92-b9cb-37a6c0e4c808",
   "metadata": {},
   "source": [
    "### Split Form 10-K sections into chunks\n",
    "- Set up text splitter using LangChain\n",
    "- For now, split only the text from the \"item 1\" section "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fd3e4843-686a-40f1-97d1-b4811084d1b1",
   "metadata": {
    "height": 115
   },
   "outputs": [],
   "source": [
    "text_splitter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size = 2000,\n",
    "    chunk_overlap  = 200,\n",
    "    length_function = len,\n",
    "    is_separator_regex = False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5acbbf0b-6d25-46ab-9539-fb38479ff4fe",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "item1_text_chunks = text_splitter.split_text(item1_text)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c35dc351-48d7-4487-b311-7224eb880c68",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "type(item1_text_chunks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc769d72-c01c-4d7b-aa3f-b12e8acd99e6",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "len(item1_text_chunks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af610b46-68f3-4763-9ffb-eb452dc61acf",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "item1_text_chunks[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "accf6522-52e3-441f-b0c3-c43d754ba55a",
   "metadata": {},
   "source": [
    "- Set up helper function to chunk all sections of the Form 10-K\n",
    "- You'll limit the number of chunks in each section to 20 to speed things up"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ed5ec07-a825-4e87-8e30-f09efa586a12",
   "metadata": {
    "height": 489
   },
   "outputs": [],
   "source": [
    "def split_form10k_data_from_file(file):\n",
    "    chunks_with_metadata = [] # use this to accumlate chunk records\n",
    "    file_as_object = json.load(open(file)) # open the json file\n",
    "    for item in ['item1','item1a','item7','item7a']: # pull these keys from the json\n",
    "        print(f'Processing {item} from {file}') \n",
    "        item_text = file_as_object[item] # grab the text of the item\n",
    "        item_text_chunks = text_splitter.split_text(item_text) # split the text into chunks\n",
    "        chunk_seq_id = 0\n",
    "        for chunk in item_text_chunks[:20]: # only take the first 20 chunks\n",
    "            form_id = file[file.rindex('/') + 1:file.rindex('.')] # extract form id from file name\n",
    "            # finally, construct a record with metadata and the chunk text\n",
    "            chunks_with_metadata.append({\n",
    "                'text': chunk, \n",
    "                # metadata from looping...\n",
    "                'f10kItem': item,\n",
    "                'chunkSeqId': chunk_seq_id,\n",
    "                # constructed metadata...\n",
    "                'formId': f'{form_id}', # pulled from the filename\n",
    "                'chunkId': f'{form_id}-{item}-chunk{chunk_seq_id:04d}',\n",
    "                # metadata from file...\n",
    "                'names': file_as_object['names'],\n",
    "                'cik': file_as_object['cik'],\n",
    "                'cusip6': file_as_object['cusip6'],\n",
    "                'source': file_as_object['source'],\n",
    "            })\n",
    "            chunk_seq_id += 1\n",
    "        print(f'\\tSplit into {chunk_seq_id} chunks')\n",
    "    return chunks_with_metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6696ea15-abf7-4f05-8fd2-8e91e5fc540c",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "first_file_chunks = split_form10k_data_from_file(first_file_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8b42f97-911f-495d-8176-a10879d2aae1",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "first_file_chunks[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58952e6c-7e9c-44eb-b52b-bb11245738a4",
   "metadata": {},
   "source": [
    "### Create graph nodes using text chunks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "968783b2-5c17-46cc-abfd-b6b5dfffe3bb",
   "metadata": {
    "height": 234
   },
   "outputs": [],
   "source": [
    "merge_chunk_node_query = \"\"\"\n",
    "MERGE(mergedChunk:Chunk {chunkId: $chunkParam.chunkId})\n",
    "    ON CREATE SET \n",
    "        mergedChunk.names = $chunkParam.names,\n",
    "        mergedChunk.formId = $chunkParam.formId, \n",
    "        mergedChunk.cik = $chunkParam.cik, \n",
    "        mergedChunk.cusip6 = $chunkParam.cusip6, \n",
    "        mergedChunk.source = $chunkParam.source, \n",
    "        mergedChunk.f10kItem = $chunkParam.f10kItem, \n",
    "        mergedChunk.chunkSeqId = $chunkParam.chunkSeqId, \n",
    "        mergedChunk.text = $chunkParam.text\n",
    "RETURN mergedChunk\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34efd2c9-ad31-440a-92c5-9394465950f9",
   "metadata": {},
   "source": [
    "- Set up connection to graph instance using LangChain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3f43dab-155e-4172-959b-e05968c0c6a8",
   "metadata": {
    "height": 64
   },
   "outputs": [],
   "source": [
    "kg = Neo4jGraph(\n",
    "    url=NEO4J_URI, username=NEO4J_USERNAME, password=NEO4J_PASSWORD, database=NEO4J_DATABASE\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddb150bf-1f77-4a5b-be0d-23ab86e32385",
   "metadata": {},
   "source": [
    "- Create a single chunk node for now"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6abdc4a7-8fb9-4cd9-8a79-8066ad67f618",
   "metadata": {
    "height": 47
   },
   "outputs": [],
   "source": [
    "kg.query(merge_chunk_node_query, \n",
    "         params={'chunkParam':first_file_chunks[0]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b368dd5-6e50-46db-b500-29993895631e",
   "metadata": {},
   "source": [
    "- Create a uniqueness constraint to avoid duplicate chunks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f8269f83-eb19-4a3f-979c-8b41b1e9f750",
   "metadata": {
    "height": 98
   },
   "outputs": [],
   "source": [
    "kg.query(\"\"\"\n",
    "CREATE CONSTRAINT unique_chunk IF NOT EXISTS \n",
    "    FOR (c:Chunk) REQUIRE c.chunkId IS UNIQUE\n",
    "\"\"\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e207e5f-eb17-452c-9c65-4846d3bbf840",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "kg.query(\"SHOW INDEXES\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b5c465e-c43f-422d-a3a1-17705a1019d5",
   "metadata": {},
   "source": [
    "- Loop through and create nodes for all chunks\n",
    "- Should create 23 nodes because you set a limit of 20 chunks in the text splitting function above"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0e4f598-a6b8-419f-b247-4eecd1f3d98a",
   "metadata": {
    "height": 166
   },
   "outputs": [],
   "source": [
    "node_count = 0\n",
    "for chunk in first_file_chunks:\n",
    "    print(f\"Creating `:Chunk` node for chunk ID {chunk['chunkId']}\")\n",
    "    kg.query(merge_chunk_node_query, \n",
    "            params={\n",
    "                'chunkParam': chunk\n",
    "            })\n",
    "    node_count += 1\n",
    "print(f\"Created {node_count} nodes\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d820104-51be-4709-b531-2bdc9b6d5eec",
   "metadata": {
    "height": 81
   },
   "outputs": [],
   "source": [
    "kg.query(\"\"\"\n",
    "         MATCH (n)\n",
    "         RETURN count(n) as nodeCount\n",
    "         \"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c46b6bfa-9e07-4963-9b3f-50cdba4468bc",
   "metadata": {},
   "source": [
    "### Create a vector index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79355493-3790-4b05-898b-a3eaaea87155",
   "metadata": {
    "height": 149
   },
   "outputs": [],
   "source": [
    "kg.query(\"\"\"\n",
    "         CREATE VECTOR INDEX `form_10k_chunks` IF NOT EXISTS\n",
    "          FOR (c:Chunk) ON (c.textEmbedding) \n",
    "          OPTIONS { indexConfig: {\n",
    "            `vector.dimensions`: 1536,\n",
    "            `vector.similarity_function`: 'cosine'    \n",
    "         }}\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "507b42c0-a90c-4f97-8c8a-b8a4ec70f9d1",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "kg.query(\"SHOW INDEXES\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b9e379f-69d1-41de-8443-c6f5660603ab",
   "metadata": {},
   "source": [
    "### Calculate embedding vectors for chunks and populate index\n",
    "- This query calculates the embedding vector and stores it as a property called `textEmbedding` on each `Chunk` node."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43f2d4f2-ea0b-4731-8b29-a858e05f5b36",
   "metadata": {
    "height": 217
   },
   "outputs": [],
   "source": [
    "kg.query(\"\"\"\n",
    "    MATCH (chunk:Chunk) WHERE chunk.textEmbedding IS NULL\n",
    "    WITH chunk, genai.vector.encode(\n",
    "      chunk.text, \n",
    "      \"OpenAI\", \n",
    "      {\n",
    "        token: $openAiApiKey, \n",
    "        endpoint: $openAiEndpoint\n",
    "      }) AS vector\n",
    "    CALL db.create.setNodeVectorProperty(chunk, \"textEmbedding\", vector)\n",
    "    \"\"\", \n",
    "    params={\"openAiApiKey\":OPENAI_API_KEY, \"openAiEndpoint\": OPENAI_ENDPOINT} )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59550c1c-5d7f-4e87-b5fc-ae7a4edb3731",
   "metadata": {
    "height": 47
   },
   "outputs": [],
   "source": [
    "kg.refresh_schema()\n",
    "print(kg.schema)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d5286ca-106c-49ec-8f60-f142db8bb680",
   "metadata": {},
   "source": [
    "### Use similarity search to find relevant chunks"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3ab0d7b-7be2-4a2b-8b2c-2ea8a35350bd",
   "metadata": {},
   "source": [
    "- Setup a help function to perform similarity search using the vector index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50153721-d565-47ac-b288-d8b9798d9479",
   "metadata": {
    "height": 251
   },
   "outputs": [],
   "source": [
    "def neo4j_vector_search(question):\n",
    "  \"\"\"Search for similar nodes using the Neo4j vector index\"\"\"\n",
    "  vector_search_query = \"\"\"\n",
    "    WITH genai.vector.encode(\n",
    "      $question, \n",
    "      \"OpenAI\", \n",
    "      {\n",
    "        token: $openAiApiKey,\n",
    "        endpoint: $openAiEndpoint\n",
    "      }) AS question_embedding\n",
    "    CALL db.index.vector.queryNodes($index_name, $top_k, question_embedding) yield node, score\n",
    "    RETURN score, node.text AS text\n",
    "  \"\"\"\n",
    "  similar = kg.query(vector_search_query, \n",
    "                     params={\n",
    "                      'question': question, \n",
    "                      'openAiApiKey':OPENAI_API_KEY,\n",
    "                      'openAiEndpoint': OPENAI_ENDPOINT,\n",
    "                      'index_name':VECTOR_INDEX_NAME, \n",
    "                      'top_k': 10})\n",
    "  return similar"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aba0f292-59e8-432c-b521-2a77613b08c6",
   "metadata": {},
   "source": [
    "- Ask a question!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6ae9975a-d26f-4477-92ac-8dcfb3119c47",
   "metadata": {
    "height": 64
   },
   "outputs": [],
   "source": [
    "search_results = neo4j_vector_search(\n",
    "    'In a single sentence, tell me about Netapp.'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2370fb5-9990-42e3-9ac2-0edf03969705",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "search_results[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5777c9ea-ca26-4690-8487-6aec06db1d4c",
   "metadata": {},
   "source": [
    "### Set up a LangChain RAG workflow to chat with the form"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17cdab04-4db1-4776-bd89-f4f87b57bde4",
   "metadata": {
    "height": 200
   },
   "outputs": [],
   "source": [
    "neo4j_vector_store = Neo4jVector.from_existing_graph(\n",
    "    embedding=OpenAIEmbeddings(),\n",
    "    url=NEO4J_URI,\n",
    "    username=NEO4J_USERNAME,\n",
    "    password=NEO4J_PASSWORD,\n",
    "    index_name=VECTOR_INDEX_NAME,\n",
    "    node_label=VECTOR_NODE_LABEL,\n",
    "    text_node_properties=[VECTOR_SOURCE_PROPERTY],\n",
    "    embedding_node_property=VECTOR_EMBEDDING_PROPERTY,\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a7abbd6-bf4e-4fc3-9c20-c4246ed3ec4e",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "retriever = neo4j_vector_store.as_retriever()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b38dd23-fa0f-4304-849a-80043e34a1e7",
   "metadata": {},
   "source": [
    "- Set up a RetrievalQAWithSourcesChain to carry out question answering\n",
    "- You can check out the LangChain documentation for this chain [here](https://api.python.langchain.com/en/latest/chains/langchain.chains.qa_with_sources.retrieval.RetrievalQAWithSourcesChain.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47b45848-112f-4e4f-9c76-cf2bda9c6f8e",
   "metadata": {
    "height": 98
   },
   "outputs": [],
   "source": [
    "chain = RetrievalQAWithSourcesChain.from_chain_type(\n",
    "    ChatOpenAI(temperature=0), \n",
    "    chain_type=\"stuff\", \n",
    "    retriever=retriever\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5aee157-c777-4c9a-949b-c55005d19c41",
   "metadata": {
    "height": 98
   },
   "outputs": [],
   "source": [
    "def prettychain(question: str) -> str:\n",
    "    \"\"\"Pretty print the chain's response to a question\"\"\"\n",
    "    response = chain({\"question\": question},\n",
    "        return_only_outputs=True,)\n",
    "    print(textwrap.fill(response['answer'], 60))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbb96301-66a2-47e9-87a7-13d926f85019",
   "metadata": {},
   "source": [
    "- Ask a question!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b234d29-7fc0-483f-bbfa-459617be96c1",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "question = \"What is Netapp's primary business?\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a2228aa-2af0-44cd-99b0-bffdc72de62b",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "prettychain(question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2fa5b5b-5ec6-4903-bb53-acf8bd5f49c3",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "prettychain(\"Where is Netapp headquartered?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "758bdd8e-90ca-4267-aef2-db33b28937b6",
   "metadata": {
    "height": 81
   },
   "outputs": [],
   "source": [
    "prettychain(\"\"\"\n",
    "    Tell me about Netapp. \n",
    "    Limit your answer to a single sentence.\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10715705-25a7-4c21-88e2-bc540c72dacb",
   "metadata": {
    "height": 81
   },
   "outputs": [],
   "source": [
    "prettychain(\"\"\"\n",
    "    Tell me about Apple. \n",
    "    Limit your answer to a single sentence.\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53300764-c93b-4127-9bbd-9e17428d066c",
   "metadata": {
    "height": 98
   },
   "outputs": [],
   "source": [
    "prettychain(\"\"\"\n",
    "    Tell me about Apple. \n",
    "    Limit your answer to a single sentence.\n",
    "    If you are unsure about the answer, say you don't know.\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1ab2124-77c4-496b-b951-44c9466fe941",
   "metadata": {},
   "source": [
    "### Ask you own question!\n",
    "- Add your own question to the call to prettychain below to find out more about NetApp\n",
    "- Here is NetApp's website if you want some inspiration: https://www.netapp.com/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f238eaec-7080-4e45-80f0-5b37e2340387",
   "metadata": {
    "height": 64
   },
   "outputs": [],
   "source": [
    "prettychain(\"\"\"\n",
    "    ADD YOUR OWN QUESTION HERE\n",
    "\"\"\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
